package com.denghq.projectbuilder.component.redis;

import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

import java.nio.charset.Charset;

//@Component
@Slf4j
public class RedisLocker {


    private final StringRedisTemplate redisTemplate;

    private static final String UNLOCK_LUA;

    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }


    public RedisLocker(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean addRedisLock(String lockKey, String requestId, long expireTime) {
        String status = redisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                Object nativeConnection = connection.getNativeConnection();
                String status = null;
                RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();

                byte[] keyByte = stringRedisSerializer.serialize(lockKey);
                byte[] valueByte = stringRedisSerializer.serialize(requestId);

                //springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包

                //lettuce连接包，集群模式，ex为秒，px为毫秒
                if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    log.debug("lettuce Cluster:---setKey:" + lockKey + "---value" + requestId + "---maxTimes:" + expireTime);
                    status = ((RedisAdvancedClusterAsyncCommands) nativeConnection)
                            .getStatefulConnection().sync()
                            .set(keyByte, valueByte, SetArgs.Builder.nx().px(expireTime));
                    log.debug("lettuce Cluster:---status:" + status);
                }
                //lettuce连接包，单机模式，ex为秒，px为毫秒
                if (nativeConnection instanceof RedisAsyncCommands) {
                    log.debug("lettuce single:---setKey:" + lockKey + "---value" + requestId + "---maxTimes:" + expireTime);
                    status = ((RedisAsyncCommands) nativeConnection)
                            .getStatefulConnection().sync()
                            .set(keyByte, valueByte, SetArgs.Builder.nx().px(expireTime));
                    log.debug("lettuce single:---status:" + status);
                }
                return status;
            }
        });
        log.debug("getLock:---status:" + status);//执行正确status="OK"
        return status != null;
    }

   /* public void unlock(String lockKey, String requestId) {

        try {
            String currentValue = redisTemplate.opsForValue().get(lockKey);
            if (!StringUtils.isEmpty(currentValue)
                    && currentValue.equals(requestId)) {
                redisTemplate.delete(lockKey);
            }
        } catch (Exception ex) {
            log.warn("unlock error");
        }
    }*/

    public Boolean unlock(String lockKey, String requestId) {

        try {
            RedisCallback<Boolean> callback = (connection) -> {
                return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN, 1, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
            };
            return (Boolean) redisTemplate.execute(callback);

        } catch (Exception ex) {
            log.warn("unlock error");
            return false;
        }

    }
}